SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
4.0 KB · 88 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { AsnBgpChart } from '@/components/detail/AsnBgpChart';4import { ComponentGrid } from '@/components/detail/ComponentGrid';5import { ScopeHeader } from '@/components/detail/ScopeHeader';6import { ScopePressureChart } from '@/components/detail/ScopeCharts';7import { TargetsTable } from '@/components/detail/Tables';8import { IncidentsSection } from '@/components/incidents/IncidentsSection';9import { Section, Stat } from '@/components/ui/primitives';10import { apiGet, apiTry } from '@/lib/api';11import { fmt, fmtDelta, fmtInt, fmtPct, fmtRatio } from '@/lib/format';12import type { AsnDetail } from '@/lib/types';1314export const dynamic = 'force-dynamic';1516type Params = Promise<{ asn: string }>;1718export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {19  const { asn } = await params;20  const a = await apiTry<AsnDetail>(`/api/v1/pressure/asn/${encodeURIComponent(asn)}`);21  if (!a) return { title: `AS${asn}` };22  return { title: `AS${a.asn} ${a.name} — pressure ${fmt(a.pressure)} (${a.level_label})`, description: `Independent observation of AS${a.asn} (${a.name}): pressure ${fmt(a.pressure)}, BGP churn ${fmtRatio(a.bgp.churn_ratio)}, path stability ${fmtPct(a.bgp.path_stability)}.`, alternates: { canonical: `/asn/${a.asn}` } };23}2425export default async function AsnPage({ params }: { params: Params }) {26  const { asn } = await params;27  if (!/^\d+$/.test(asn)) {28    const { notFound } = await import('next/navigation');29    notFound();30  }31  const a = await apiGet<AsnDetail>(`/api/v1/pressure/asn/${encodeURIComponent(asn)}`);32  return (33    <div className="pb-8">34      <ScopeHeader35        kicker={`Autonomous system · ${a.country} · importance ${a.importance}/5`}36        title={`AS${a.asn} ${a.name}`}37        subtitle={<>Observed from {a.regions_observed.length} probe regions · {fmtInt(a.targets.length)} targets announced from this network</>}38        pressure={a.pressure}39        level={a.level}40        delta1h={a.delta_1h}41        trend={a.trend}42        confidence={a.confidence}43        ts={a.ts}44        meta={45          <>46            <span>47              regions{' '}48              {a.regions_observed.map((r) => (49                <Link key={r} href={`/internet/${r}`} className="mr-1 text-ink hover:text-accent">50                  {r}51                </Link>52              ))}53            </span>54          </>55        }56      />5758      <Section label="Components">59        <ComponentGrid components={a.components} />60      </Section>6162      <Section label="BGP" right={<Link href="/bgp" className="hover:text-ink">global BGP dashboard →</Link>}>63        <div className="mb-4 grid grid-cols-3 gap-4 sm:grid-cols-6">64          <Stat label="prefixes 24h" value={fmtInt(a.bgp.prefixes_observed_24h)} />65          <Stat label="announcements 1h" value={fmtInt(a.bgp.announcements_1h)} />66          <Stat label="withdrawals 1h" value={fmtInt(a.bgp.withdrawals_1h)} />67          <Stat label="churn ratio" value={<span style={{ color: a.bgp.churn_ratio >= 2 ? 'var(--p-high)' : undefined }}>{fmtRatio(a.bgp.churn_ratio)}</span>} sub="vs baseline" />68          <Stat label="origin changes 1h" value={<span style={{ color: a.bgp.origin_changes_1h > 0 ? 'var(--p-elevated)' : undefined }}>{fmtInt(a.bgp.origin_changes_1h)}</span>} />69          <Stat label="path stability" value={<span style={{ color: a.bgp.path_stability < 0.9 ? 'var(--p-stressed)' : undefined }}>{fmtPct(a.bgp.path_stability)}</span>} />70        </div>71        <AsnBgpChart series={a.bgp.series_24h} />72      </Section>7374      <Section label="24 h pressure">75        <ScopePressureChart series={a.history_24h} height={220} />76      </Section>7778      <Section label="Incidents involving this ASN">79        <IncidentsSection incidents={a.incidents} />80      </Section>8182      <Section label="Targets" right={<span>{fmtInt(a.targets.length)} targets · Δ1h {fmtDelta(a.delta_1h)}</span>}>83        <TargetsTable targets={a.targets} />84      </Section>85    </div>86  );87}88